﻿/*
 * This file is part of MonoStrategy.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://monostrategy.codeplex.com/
 */
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using MonoStrategy.RenderSystem;

namespace MonoStrategy
{

    [ObfuscationAttribute(Feature = "renaming", ApplyToMembers = true)]
    [XmlRoot("GUIConfig")]
    public class XMLGUIConfig
    {
        private readonly SortedDictionary<String, Object> m_Context = new SortedDictionary<string, object>();

        [XmlIgnore]
        public DateTime LastWriteTime { get; private set; }
        [XmlIgnore]
        public String ConfigDirectory { get; private set; }
        [XmlIgnore]
        public GLRenderer Renderer { get; private set; }

        [ObfuscationAttribute(Feature = "renaming", ApplyToMembers = true)]
        public class TileEntry
        {
            [XmlAttribute]
            public int Index { get; set; }
            [XmlAttribute]
            public int X { get; set; }
            [XmlAttribute]
            public int Y { get; set; }
            [XmlAttribute]
            public int Width { get; set; }
            [XmlAttribute]
            public int Height { get; set; }
        }

        [ObfuscationAttribute(Feature = "renaming", ApplyToMembers = true)]
        public class AtlasEntry
        {
            [XmlAttribute]
            public String Name { get; set; }
            [XmlArray("TileList"), XmlArrayItem("Tile")]
            public TileEntry[] Tiles { get; set; }

            public void Process(XMLGUIConfig config)
            {
                if (String.IsNullOrEmpty(Name))
                    throw new ArgumentException("No name given for object.");

                if (config.m_Context.ContainsKey(Name))
                    throw new ArgumentException("There is already an object with name \"" + Name + "\" registered in GUI config.");

                config.m_Context.Add(Name, this);
            }
        }

        [ObfuscationAttribute(Feature = "renaming", ApplyToMembers = true)]
        public class FrameEntry
        {
            [XmlAttribute("Name")]
            public String Name { get; set; }
            [XmlAttribute("Atlas")]
            public String AtlasString { get; set; }
            [XmlAttribute("Image")]
            public String ImageString { get; set; }
            [XmlIgnore]
            public int ImageID { get; private set; }
            [XmlIgnore]
            public AtlasEntry Atlas { get; private set; }

            public void Process(XMLGUIConfig config)
            {
                ImageEntry imageEntry = config.GetImage(ImageString);

                ImageID = imageEntry.ImageID;

                if (String.IsNullOrEmpty(Name))
                    throw new ArgumentException("No name given for object.");

                if (String.IsNullOrEmpty(AtlasString))
                    throw new ArgumentException("No atlas given for object.");

                if (config.m_Context.ContainsKey(Name))
                    throw new ArgumentException("There is already an object with name \"" + Name + "\" registered in GUI config.");

                config.m_Context.Add(Name, this);

                Atlas = (AtlasEntry)config.m_Context[AtlasString];

                // check range
                int width = imageEntry.Image.Width;
                int height = imageEntry.Image.Height;

                foreach (var tile in Atlas.Tiles)
                {
                    if ((tile.X < 0) || (tile.Y < 0) || (tile.X >= width - 1) || (tile.Y >= height - 1) ||
                            (tile.Width < 0) || (tile.Height < 0) || (tile.X + tile.Width > width) ||
                            (tile.Y + tile.Height > height))
                        throw new ArgumentOutOfRangeException("Frame \"" + Name + "\" does not match the given atlas tiles.");
                }
            }
        }

        [ObfuscationAttribute(Feature = "renaming", ApplyToMembers = true)]
        public class ImageEntry
        {
            private static int s_ImageID = 0;

            public String Name { get; private set; }
            public String Path { get; private set; }
            public System.Drawing.Bitmap Image { get; private set; }
            public int ImageID { get; private set; }

            internal ImageEntry(XMLGUIConfig inParent, String inResourceName)
            {
                Name = inResourceName;
                Path = System.IO.Path.GetFullPath(inParent.ConfigDirectory + Name);
                ImageID = System.Threading.Interlocked.Increment(ref s_ImageID);
                inParent.Renderer.RegisterImage(ImageID, Image = (System.Drawing.Bitmap)System.Drawing.Bitmap.FromFile(Path));
            }

            internal ImageEntry(XMLGUIConfig inParent, System.Drawing.Bitmap inBitmap)
            {
                ImageID = System.Threading.Interlocked.Increment(ref s_ImageID);
                inParent.Renderer.RegisterImage(ImageID, Image = inBitmap);
            }
        }

        [XmlArray("AtlasList"), XmlArrayItem("Atlas")]
        public List<AtlasEntry> AtlasList { get; set; }
        [XmlArray("FrameList"), XmlArrayItem("Frame")]
        public List<FrameEntry> FrameList { get; set; }

        public static XMLGUIConfig Load(GLRenderer inRenderer, String inFileName)
        {
            if (inRenderer == null)
                throw new ArgumentNullException();

            // load config XML
            XmlSerializer format = new XmlSerializer(typeof(XMLGUIConfig));
            Stream stream = File.OpenRead(inFileName);
            XMLGUIConfig configXML;

            using (stream)
            {
                configXML = (XMLGUIConfig)format.Deserialize(stream);

                var fileInfo = new FileInfo(inFileName);

                configXML.ConfigDirectory = Path.GetDirectoryName(fileInfo.FullName) + "/";
                configXML.LastWriteTime = fileInfo.LastWriteTime;
            }

            configXML.Process(inRenderer);

            return configXML;
        }

        private void Process(GLRenderer inRenderer)
        {
            m_Context.Clear();

            Renderer = inRenderer;

            // postprocess 
            foreach (var atlas in AtlasList)
            {
                atlas.Process(this);
            }

            foreach (var frame in FrameList)
            {
                frame.Process(this);
            }
        }

        public Boolean Exists(String inResource)
        {
            return File.Exists(ConfigDirectory + inResource);
        }

        public FrameEntry GetFrame(String inName)
        {
            if (!m_Context.ContainsKey(inName))
                throw new KeyNotFoundException("A frame named \"" + inName + "\" does not exist.");

            return (FrameEntry)m_Context[inName];
        }

        public int RegisterImage(System.Drawing.Bitmap inBitmap)
        {
            return new ImageEntry(this, inBitmap).ImageID;
        }

        public ImageEntry GetImage(String inResourceName)
        {
            if (!m_Context.ContainsKey(inResourceName))
            {
                if (!Exists(inResourceName))
                    throw new KeyNotFoundException("An image named \"" + inResourceName + "\" does not exist.");

                m_Context.Add(inResourceName, new ImageEntry(this, inResourceName));
            }

            return (ImageEntry)m_Context[inResourceName];
        }

    }
}
